@dereekb/dbx-form 13.11.1 → 13.11.3

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,7 +6,7 @@ import { isPast, addSeconds, startOfDay, addMinutes, addDays, isBefore } from 'd
6
6
  import { map, of, shareReplay, switchMap, first, exhaustMap, catchError, delay, filter, combineLatest, distinctUntilChanged, BehaviorSubject, Subject, startWith, throttleTime, scan, timer, merge, EMPTY, skip, debounceTime, combineLatestWith, interval, tap, withLatestFrom, mergeMap } from 'rxjs';
7
7
  import * as i1$2 from '@dereekb/dbx-core';
8
8
  import { DbxActionContextStoreSourceInstance, cleanLockSet, cleanSubscription, completeOnDestroy, cleanWithLockSet, GetValuePipe, DateDistancePipe, TimeDistancePipe, DbxInjectionComponent, mergeDbxInjectionComponentConfigs } from '@dereekb/dbx-core';
9
- import { makeIsModifiedFunctionObservable, asObservable, LockSet, filterMaybe, switchMapFilterMaybe, scanCount, errorOnEmissionsInPeriod, asObservableFromGetter, tapLog, maybeValueFromObservableOrValue, valueFromFinishedLoadingState, switchMapMaybeDefault, skipAllInitialMaybe, successResult, startWithBeginLoading, skipUntilTimeElapsedAfterLastEmission, mapLoadingStateResults, isLoadingStateWithDefinedValue, isLoadingStateLoading, beginLoading, mapLoadingStateValueWithOperator, loadingStateContext, distinctUntilHasDifferentValues, SimpleLoadingContext, listLoadingStateContext, mapIsListLoadingStateWithEmptyValue, isLoadingStateInLoadingState, asyncPusherCache } from '@dereekb/rxjs';
9
+ import { makeIsModifiedFunctionObservable, asObservable, LockSet, filterMaybe, switchMapFilterMaybe, scanCount, errorOnEmissionsInPeriod, asObservableFromGetter, maybeValueFromObservableOrValue, valueFromFinishedLoadingState, switchMapMaybeDefault, skipAllInitialMaybe, successResult, startWithBeginLoading, skipUntilTimeElapsedAfterLastEmission, mapLoadingStateResults, isLoadingStateWithDefinedValue, isLoadingStateLoading, beginLoading, mapLoadingStateValueWithOperator, loadingStateContext, distinctUntilHasDifferentValues, SimpleLoadingContext, listLoadingStateContext, mapIsListLoadingStateWithEmptyValue, isLoadingStateInLoadingState, asyncPusherCache } from '@dereekb/rxjs';
10
10
  import { toObservable, toSignal, rxResource } from '@angular/core/rxjs-interop';
11
11
  import { BooleanStringKeyArrayUtility, iterablesAreSetEquivalent, filterUndefinedValues, filterMaybeArrayValues, filterUniqueValues, areEqualPOJOValuesUsingPojoFilter, NOOP_MODIFIER, asArray, mergeArrays, filterNullAndUndefinedValues, objectHasNoKeys, mapMaybeFunction, isWebsiteUrlWithPrefix, websiteUrlDetails, transformStringFunction, US_STATE_CODE_STRING_REGEX, ZIP_CODE_STRING_REGEX, LAT_LNG_PATTERN, transformNumberFunction, DOLLAR_AMOUNT_PRECISION, stripObject, getValueFromGetter, asGetter, dateFromMinuteOfDay, dateToMinuteOfDay, isISO8601DayStringStart, mapIdentityFunction, isDate, MS_IN_MINUTE, isMonthDaySlashDate, filterFromPOJO, TIME_UNIT_SHORT_LABEL_MAP, timeUnitToMilliseconds, ALL_TIME_UNITS, minutesToHoursAndMinutes, millisecondsToTimeUnit, hoursAndMinutesToTimeUnit, isE164PhoneNumber as isE164PhoneNumber$1, isValidPhoneExtensionNumber, e164PhoneNumberExtensionPair, e164PhoneNumberFromE164PhoneNumberExtensionPair, mergeArraysIntoArray, convertMaybeToArray, lastValue, filterEmptyArrayValues, setContainsAllValues, addToSetCopy, setsAreEquivalent, makeValuesGroupMap, sortByStringFunction, separateValues, isSelectedDecisionFunctionFactory, readKeysFrom, hasDifferentValues, capitalizeFirstLetter, objectIsEmpty, mergeObjectsFunction, filterFromPOJOFunction, mergeObjects, addPlusPrefixToNumber, searchStringFilterFunction, caseInsensitiveFilterByIndexOfDecisionFactory, arrayToMap, firstValue, cachedGetter, makeGetter, asDecisionFunction, TIME_UNIT_LABEL_MAP, HAS_WEBSITE_DOMAIN_NAME_REGEX, KeyValueTypleValueFilter, valuesFromPOJO, allObjectsAreEqual, isNumberDivisibleBy, nearestDivisibleValues, concatArrays } from '@dereekb/util';
12
12
  import * as i1 from '@angular/forms';
@@ -1166,6 +1166,11 @@ function isEmptyFormValue(val) {
1166
1166
  * from a form value object. Also removes keys whose values become empty objects
1167
1167
  * `{}` after recursive stripping.
1168
1168
  *
1169
+ * Arrays are recursed into so that empties inside nested objects are stripped,
1170
+ * but array length and item indices are preserved — primitive empty values
1171
+ * (e.g. `NaN`, `''`) inside an array stay in place, since shifting indices would
1172
+ * change the semantics of chip/list-style array fields.
1173
+ *
1169
1174
  * This normalizes ng-forge output to match ngx-formly behavior, where the model
1170
1175
  * only includes keys that have been explicitly set by the user.
1171
1176
  *
@@ -1176,6 +1181,9 @@ function isEmptyFormValue(val) {
1176
1181
  *
1177
1182
  * stripEmptyForgeValues({ section: { a: "", b: "" } })
1178
1183
  * // → {}
1184
+ *
1185
+ * stripEmptyForgeValues({ items: [{ amount: NaN, name: 'a' }, { amount: 5 }] })
1186
+ * // → { items: [{ name: 'a' }, { amount: 5 }] }
1179
1187
  * ```
1180
1188
  *
1181
1189
  * @param value - The form value object to clean
@@ -1183,18 +1191,24 @@ function isEmptyFormValue(val) {
1183
1191
  */
1184
1192
  function stripEmptyForgeValues(value) {
1185
1193
  let result;
1186
- if (value == null || typeof value !== 'object' || Array.isArray(value)) {
1194
+ if (value == null || typeof value !== 'object' || value instanceof Date) {
1187
1195
  result = value;
1188
1196
  }
1197
+ else if (Array.isArray(value)) {
1198
+ result = value.map((item) => stripEmptyForgeValues(item));
1199
+ }
1189
1200
  else {
1190
1201
  const stripped = {};
1191
1202
  for (const [key, val] of Object.entries(value)) {
1192
1203
  if (isEmptyFormValue(val)) {
1193
1204
  continue;
1194
1205
  }
1195
- if (typeof val === 'object' && !Array.isArray(val) && !(val instanceof Date)) {
1206
+ if (typeof val === 'object' && !(val instanceof Date)) {
1196
1207
  const cleaned = stripEmptyForgeValues(val);
1197
- if (cleaned != null && Object.keys(cleaned).length > 0) {
1208
+ if (Array.isArray(cleaned)) {
1209
+ stripped[key] = cleaned;
1210
+ }
1211
+ else if (cleaned != null && Object.keys(cleaned).length > 0) {
1198
1212
  stripped[key] = cleaned;
1199
1213
  }
1200
1214
  }
@@ -1236,7 +1250,7 @@ class DbxForgeFormContext {
1236
1250
  */
1237
1251
  stripInternalKeys = true;
1238
1252
  /**
1239
- * When true (default), keys whose values are empty (`null`, `undefined`, or `""`)
1253
+ * When true (default), keys whose values are empty (`null`, `undefined`, `""`, or `NaN`)
1240
1254
  * are stripped from the form value before emission. This normalizes ng-forge output
1241
1255
  * to match ngx-formly behavior, where the model only includes keys that have been
1242
1256
  * explicitly set by the user.
@@ -1323,7 +1337,7 @@ class DbxForgeFormContext {
1323
1337
  _isValid = new BehaviorSubject(false);
1324
1338
  _setValue = new BehaviorSubject(undefined);
1325
1339
  _reset = new BehaviorSubject(new Date());
1326
- _internalConfig$ = this._config.pipe(tapLog('internal config'), scan((acc, config) => {
1340
+ _internalConfig$ = this._config.pipe(scan((acc, config) => {
1327
1341
  let result;
1328
1342
  if (config) {
1329
1343
  if (acc?.input !== config) {
@@ -1337,8 +1351,8 @@ class DbxForgeFormContext {
1337
1351
  result = undefined;
1338
1352
  }
1339
1353
  return result;
1340
- }, undefined), tapLog('internal config result'), shareReplay(1));
1341
- config$ = this._internalConfig$.pipe(filterMaybe(), map(({ config }) => config), tapLog('config'), shareReplay(1));
1354
+ }, undefined), shareReplay(1));
1355
+ config$ = this._internalConfig$.pipe(filterMaybe(), map(({ config }) => config), shareReplay(1));
1342
1356
  /**
1343
1357
  * Form event stream that restarts on each reset, mirroring the formly form's
1344
1358
  * switchMap-on-reset pattern. This ensures that each resetForm() produces a fresh
@@ -1577,7 +1591,7 @@ class DbxForgeFormComponent {
1577
1591
  _disabledSub = cleanSubscription();
1578
1592
  dynamicForm = viewChild(DynamicForm, ...(ngDevMode ? [{ debugName: "dynamicForm" }] : /* istanbul ignore next */ []));
1579
1593
  formValue = signal({}, { ...(ngDevMode ? { debugName: "formValue" } : /* istanbul ignore next */ {}), equal: (a, b) => _forgeFormValueEqual(a, b, this._context) });
1580
- configSignal = toSignal(this._context.config$.pipe(tapLog('forge.config')), { initialValue: undefined });
1594
+ configSignal = toSignal(this._context.config$, { initialValue: undefined });
1581
1595
  _changesCount = signal(0, ...(ngDevMode ? [{ debugName: "_changesCount" }] : /* istanbul ignore next */ []));
1582
1596
  _lastResetAt = signal(new Date(), ...(ngDevMode ? [{ debugName: "_lastResetAt" }] : /* istanbul ignore next */ []));
1583
1597
  _isReset = signal(true, ...(ngDevMode ? [{ debugName: "_isReset" }] : /* istanbul ignore next */ []));
@@ -1996,8 +2010,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
1996
2010
  class DbxFormLoggerDirective {
1997
2011
  form = inject(DbxForm, { host: true });
1998
2012
  constructor() {
1999
- cleanSubscription(this.form.stream$.subscribe((event) => {
2000
- console.log('dbxFormLogger - stream: ', event);
2013
+ cleanSubscription(combineLatest([this.form.getValue(), this.form.stream$]).subscribe(([currentValue, event]) => {
2014
+ console.log('dbxFormLogger - stream: ', { currentValue, event });
2001
2015
  }));
2002
2016
  }
2003
2017
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFormLoggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -2158,6 +2172,7 @@ const SELF_DEPENDENCY_TOKEN = '$self'; // TODO: Import from ng-forge?
2158
2172
  * })
2159
2173
  * });
2160
2174
  * ```
2175
+ * @__NO_SIDE_EFFECTS__
2161
2176
  */
2162
2177
  function dbxForgeFieldFunction(config) {
2163
2178
  const { type, buildFieldDef, buildProps } = config;
@@ -4308,6 +4323,7 @@ var DbxDateTimeValueMode;
4308
4323
  * const parser = dbxDateTimeInputValueParseFactory(DbxDateTimeValueMode.DATE_STRING, timezoneInstance);
4309
4324
  * const date = parser('2024-01-15T10:00:00Z');
4310
4325
  * ```
4326
+ * @__NO_SIDE_EFFECTS__
4311
4327
  */
4312
4328
  function dbxDateTimeInputValueParseFactory(mode, timezoneInstance) {
4313
4329
  let factory;
@@ -4385,6 +4401,7 @@ function dbxDateTimeInputValueParseFactory(mode, timezoneInstance) {
4385
4401
  * const formatter = dbxDateTimeOutputValueFactory(DbxDateTimeValueMode.DAY_STRING, null);
4386
4402
  * const dayString = formatter(new Date()); // e.g., '2024-01-15'
4387
4403
  * ```
4404
+ * @__NO_SIDE_EFFECTS__
4388
4405
  */
4389
4406
  function dbxDateTimeOutputValueFactory(mode, timezoneInstance) {
4390
4407
  let factory;
@@ -5157,6 +5174,7 @@ function dateTimeFieldCalc() {
5157
5174
  *
5158
5175
  * @param input - The datetime calculation input containing date, time, and mode information
5159
5176
  * @returns The combined Date value, or undefined if the input is cleared or incomplete
5177
+ * @__NO_SIDE_EFFECTS__
5160
5178
  */
5161
5179
  function buildCombinedDateTime(input) {
5162
5180
  const { dateValue, timeString, isFullDay, fullDayInUTC, isTimeOnly, timeMode, timeDate, isCleared } = input;
@@ -8105,8 +8123,8 @@ class DbxForgeValueSelectionFieldComponent {
8105
8123
  addClearOptionSignal = computed(() => this.props()?.addClearOption, ...(ngDevMode ? [{ debugName: "addClearOptionSignal" }] : /* istanbul ignore next */ []));
8106
8124
  addClearOption$ = toObservable(this.addClearOptionSignal).pipe(distinctUntilChanged());
8107
8125
  inputOptionsSignal = computed(() => this.props()?.options, ...(ngDevMode ? [{ debugName: "inputOptionsSignal" }] : /* istanbul ignore next */ []));
8108
- inputOptions$ = toObservable(this.inputOptionsSignal).pipe(tapLog('yyy'), maybeValueFromObservableOrValue());
8109
- resolvedOptions$ = combineLatest([this.inputOptions$, this.addClearOption$]).pipe(tapLog('xxxx'), map(([options, addClearOption]) => resolveForgeSelectionOptions(options ?? [], addClearOption ?? false)));
8126
+ inputOptions$ = toObservable(this.inputOptionsSignal).pipe(maybeValueFromObservableOrValue());
8127
+ resolvedOptions$ = combineLatest([this.inputOptions$, this.addClearOption$]).pipe(map(([options, addClearOption]) => resolveForgeSelectionOptions(options ?? [], addClearOption ?? false)));
8110
8128
  resolvedOptionsSignal = toSignal(this.resolvedOptions$);
8111
8129
  multipleSignal = computed(() => this.props()?.multiple ?? false, ...(ngDevMode ? [{ debugName: "multipleSignal" }] : /* istanbul ignore next */ []));
8112
8130
  effectiveAppearance = computed(() => this.props()?.appearance ?? this.materialConfig?.appearance ?? 'outline', ...(ngDevMode ? [{ debugName: "effectiveAppearance" }] : /* istanbul ignore next */ []));
@@ -11251,6 +11269,7 @@ function dbxForgeUsernameLoginField(username) {
11251
11269
  * When the search string is empty, the system timezone is returned first, followed by all timezones.
11252
11270
  *
11253
11271
  * @returns A {@link SearchableValueFieldStringSearchFn} for searching timezone values.
11272
+ * @__NO_SIDE_EFFECTS__
11254
11273
  */
11255
11274
  function timezoneStringSearchFunction() {
11256
11275
  const timezoneInfos = allTimezoneInfos();
@@ -14269,6 +14288,7 @@ function formlyValueSelectionField(config) {
14269
14288
  *
14270
14289
  * @param label - Optional label for the clear option
14271
14290
  * @returns A function that transforms selection options by prepending a clear option
14291
+ * @__NO_SIDE_EFFECTS__
14272
14292
  */
14273
14293
  function formlyAddValueSelectionOptionFunction(label) {
14274
14294
  return (options) => {