@form-eng/core 1.1.1 → 1.2.0

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.
package/dist/index.mjs CHANGED
@@ -488,7 +488,27 @@ function resetValueFunctionRegistry() {
488
488
  }
489
489
 
490
490
  // src/helpers/ExpressionEngine.ts
491
+ import { Parser } from "expr-eval";
492
+ var _parser = new Parser();
493
+ var _origAdd = _parser.binaryOps["+"];
494
+ _parser.binaryOps["+"] = (a, b) => {
495
+ if (typeof a === "string" || typeof b === "string") return String(a) + String(b);
496
+ return _origAdd(a, b);
497
+ };
498
+ _parser.consts["NaN"] = NaN;
491
499
  function evaluateExpression(expression, values, fieldName, parentEntity, currentUserId) {
500
+ const singleFnMatch = /^\s*\$fn\.([a-zA-Z_][a-zA-Z0-9_]*)\(\)\s*$/.exec(expression);
501
+ if (singleFnMatch) {
502
+ const fn = getValueFunction(singleFnMatch[1]);
503
+ if (!fn) return void 0;
504
+ return fn({
505
+ fieldName: fieldName ?? "",
506
+ fieldValue: fieldName ? values[fieldName] : void 0,
507
+ values,
508
+ parentEntity,
509
+ currentUserId
510
+ });
511
+ }
492
512
  let resolved = expression.replace(
493
513
  /\$fn\.([a-zA-Z_][a-zA-Z0-9_]*)\(\)/g,
494
514
  (_, fnName) => {
@@ -501,48 +521,35 @@ function evaluateExpression(expression, values, fieldName, parentEntity, current
501
521
  parentEntity,
502
522
  currentUserId
503
523
  });
504
- if (result === null || result === void 0) return "undefined";
524
+ if (result === null || result === void 0) return "NaN";
505
525
  if (typeof result === "string") return JSON.stringify(result);
506
- if (result instanceof Date) return `new Date(${result.getTime()})`;
526
+ if (result instanceof Date) return String(result.getTime());
507
527
  return String(result);
508
528
  }
509
- return "undefined";
529
+ return "NaN";
510
530
  }
511
531
  );
532
+ const serializeValue = (value) => {
533
+ if (value === null || value === void 0) return "NaN";
534
+ if (typeof value === "string") return JSON.stringify(value);
535
+ if (typeof value === "boolean") return value ? "true" : "false";
536
+ if (value instanceof Date) return String(value.getTime());
537
+ return String(value);
538
+ };
512
539
  resolved = resolved.replace(
513
540
  /\$parent\.([a-zA-Z_][a-zA-Z0-9_.]*)/g,
514
- (_, fieldPath) => {
515
- const value = getNestedValue2(parentEntity ?? {}, fieldPath);
516
- if (value === null || value === void 0) return "undefined";
517
- if (typeof value === "string") return JSON.stringify(value);
518
- return String(value);
519
- }
541
+ (_, fieldPath) => serializeValue(getNestedValue2(parentEntity ?? {}, fieldPath))
520
542
  );
521
543
  resolved = resolved.replace(
522
544
  /\$root\.([a-zA-Z_][a-zA-Z0-9_.]*)/g,
523
- (_, fieldPath) => {
524
- const value = getNestedValue2(values, fieldPath);
525
- if (value === null || value === void 0) return "undefined";
526
- if (typeof value === "string") return JSON.stringify(value);
527
- return String(value);
528
- }
545
+ (_, fieldPath) => serializeValue(getNestedValue2(values, fieldPath))
529
546
  );
530
547
  resolved = resolved.replace(
531
548
  /\$values\.([a-zA-Z_][a-zA-Z0-9_.]*)/g,
532
- (_, fieldPath) => {
533
- const value = getNestedValue2(values, fieldPath);
534
- if (value === null || value === void 0) return "undefined";
535
- if (typeof value === "string") return JSON.stringify(value);
536
- return String(value);
537
- }
549
+ (_, fieldPath) => serializeValue(getNestedValue2(values, fieldPath))
538
550
  );
539
551
  try {
540
- const safeEval = new Function(
541
- "Math",
542
- "Date",
543
- `"use strict"; return (${resolved});`
544
- );
545
- return safeEval(Math, Date);
552
+ return _parser.evaluate(resolved, {});
546
553
  } catch {
547
554
  return void 0;
548
555
  }
@@ -1844,6 +1851,7 @@ var RenderField = (props) => {
1844
1851
  readOnly,
1845
1852
  disabled,
1846
1853
  options,
1854
+ optionsLoading,
1847
1855
  validate,
1848
1856
  parentEntityId,
1849
1857
  parentEntityType,
@@ -1938,6 +1946,7 @@ var RenderField = (props) => {
1938
1946
  savePending,
1939
1947
  config,
1940
1948
  options,
1949
+ optionsLoading,
1941
1950
  label,
1942
1951
  type,
1943
1952
  description,
@@ -1970,6 +1979,7 @@ var RenderField = (props) => {
1970
1979
  readOnly,
1971
1980
  disabled,
1972
1981
  options,
1982
+ optionsLoading,
1973
1983
  softHidden,
1974
1984
  renderLabel,
1975
1985
  renderError,
@@ -2110,6 +2120,7 @@ var ConfirmInputsModal_default = ConfirmInputsModal;
2110
2120
 
2111
2121
  // src/components/InlineFormFields.tsx
2112
2122
  import React9 from "react";
2123
+ import { useFormContext as useFormContext4 } from "react-hook-form";
2113
2124
 
2114
2125
  // src/components/FormErrorBoundary.tsx
2115
2126
  import React8 from "react";
@@ -2169,6 +2180,33 @@ var FormFields = (props) => {
2169
2180
  renderStatus,
2170
2181
  analytics
2171
2182
  } = props;
2183
+ const [asyncOptions, setAsyncOptions] = React9.useState({});
2184
+ const [asyncOptionsLoading, setAsyncOptionsLoading] = React9.useState({});
2185
+ const asyncCacheKeyRef = React9.useRef({});
2186
+ const { getValues } = useFormContext4();
2187
+ React9.useEffect(() => {
2188
+ if (!fields) return;
2189
+ Object.entries(fields).forEach(([fieldId, fieldConfig]) => {
2190
+ if (!fieldConfig.loadOptions) return;
2191
+ const depValues = (fieldConfig.optionsDependsOn ?? []).reduce(
2192
+ (acc, dep) => {
2193
+ acc[dep] = getValues(dep);
2194
+ return acc;
2195
+ },
2196
+ {}
2197
+ );
2198
+ const cacheKey = JSON.stringify(depValues);
2199
+ if (asyncCacheKeyRef.current[fieldId] === cacheKey) return;
2200
+ asyncCacheKeyRef.current[fieldId] = cacheKey;
2201
+ setAsyncOptionsLoading((prev) => ({ ...prev, [fieldId]: true }));
2202
+ fieldConfig.loadOptions({ fieldId, values: getValues() }).then((options) => {
2203
+ setAsyncOptions((prev) => ({ ...prev, [fieldId]: options }));
2204
+ }).catch(() => {
2205
+ }).finally(() => {
2206
+ setAsyncOptionsLoading((prev) => ({ ...prev, [fieldId]: false }));
2207
+ });
2208
+ });
2209
+ });
2172
2210
  const collapsedClass = !isExpanded && (expandEnabled || expandEnabled === void 0) ? "collapsed" : "";
2173
2211
  const fieldsToRender = GetFieldsToRender(fieldRenderLimit ?? 0, fieldOrder ?? [], formState?.fieldStates);
2174
2212
  const loadingKey = `${programName}-${entityType}-${entityId}-form-loaded`;
@@ -2196,7 +2234,8 @@ var FormFields = (props) => {
2196
2234
  hidden: fieldState.hidden,
2197
2235
  required: fieldState.required,
2198
2236
  readOnly: fieldState.readOnly,
2199
- options: fieldState.options,
2237
+ options: asyncOptions[fieldName] ?? fieldState.options,
2238
+ optionsLoading: asyncOptionsLoading[fieldName] ?? false,
2200
2239
  validate: fieldState.validate,
2201
2240
  parentEntityId,
2202
2241
  parentEntityType,
@@ -2254,9 +2293,13 @@ var FormEngine = (props) => {
2254
2293
  renderDialog,
2255
2294
  renderSaveButton,
2256
2295
  formErrors,
2296
+ fieldErrors,
2257
2297
  renderLabel,
2258
2298
  renderError,
2259
- renderStatus
2299
+ renderStatus,
2300
+ onSubmit: onSubmitProp,
2301
+ onSubmitError,
2302
+ renderSubmitButton
2260
2303
  } = props;
2261
2304
  const fields = formConfig?.fields ?? props.fieldConfigs ?? {};
2262
2305
  const formSettings = formConfig?.settings;
@@ -2292,6 +2335,12 @@ var FormEngine = (props) => {
2292
2335
  React10.useEffect(() => {
2293
2336
  initForm(defaultValues);
2294
2337
  }, [areAllFieldsReadonly]);
2338
+ React10.useEffect(() => {
2339
+ if (!fieldErrors) return;
2340
+ Object.entries(fieldErrors).forEach(([fieldName, message]) => {
2341
+ setError(fieldName, { type: "server", message });
2342
+ });
2343
+ }, [fieldErrors]);
2295
2344
  const initForm = (entityData) => {
2296
2345
  const { formState: loadedState, initEntityData } = isCreate ? InitOnCreateFormState(configName, fields, entityData, parentEntity ?? {}, currentUserId ?? "", setValue, initFormState) : InitOnEditFormState(configName, fields, entityData, areAllFieldsReadonly ?? false, initFormState);
2297
2346
  setExpandEnabled(IsExpandVisible(loadedState.fieldStates, effectiveExpandCutoff));
@@ -2437,6 +2486,26 @@ var FormEngine = (props) => {
2437
2486
  }
2438
2487
  });
2439
2488
  };
2489
+ const handleFormSubmit = React10.useCallback(() => {
2490
+ if (!onSubmitProp) return;
2491
+ const cfg = rulesStateRef.current.configs[configName];
2492
+ if (cfg?.fieldStates) {
2493
+ Object.keys(cfg.fieldStates).forEach((fieldName) => {
2494
+ if (cfg.fieldStates[fieldName]?.hidden) clearErrors(fieldName);
2495
+ });
2496
+ }
2497
+ trigger().then((valid) => {
2498
+ if (!valid) {
2499
+ setIsExpanded(true);
2500
+ (typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : setTimeout)(() => focusFirstError());
2501
+ onSubmitError?.(formStateRef.current.errors);
2502
+ } else {
2503
+ const values = formMethods.getValues();
2504
+ Promise.resolve(onSubmitProp(values)).catch(() => {
2505
+ });
2506
+ }
2507
+ });
2508
+ }, [onSubmitProp, onSubmitError, configName, trigger, clearErrors]);
2440
2509
  const onFilterChange = React10.useCallback((value) => {
2441
2510
  if (filterTimeoutRef.current) clearTimeout(filterTimeoutRef.current);
2442
2511
  filterTimeoutRef.current = setTimeout(() => setFilterText(value), 500);
@@ -2492,7 +2561,8 @@ var FormEngine = (props) => {
2492
2561
  reset();
2493
2562
  initForm(defaultValues);
2494
2563
  }, disabled: !isDirty || isSubmitting, children: FormStrings.cancel })
2495
- ] }))
2564
+ ] })),
2565
+ onSubmitProp && (renderSubmitButton ? renderSubmitButton({ onSubmit: handleFormSubmit, isDirty, isValid, isSubmitting }) : /* @__PURE__ */ jsx10("div", { className: "fe-submit-actions", style: { marginTop: "16px" }, children: /* @__PURE__ */ jsx10("button", { type: "button", className: "submit-button", onClick: handleFormSubmit, disabled: isSubmitting, children: FormStrings.save }) }))
2496
2566
  ] }),
2497
2567
  /* @__PURE__ */ jsx10(
2498
2568
  ConfirmInputsModal_default,
@@ -4121,6 +4191,86 @@ function useBeforeUnload(shouldWarn, message, onAbandonment) {
4121
4191
  }, [shouldWarn, message]);
4122
4192
  }
4123
4193
 
4194
+ // src/helpers/FieldUtils.ts
4195
+ var GetFieldDataTestId = (fieldName, programName, entityType, entityId) => {
4196
+ return `${programName}-${entityType}-${entityId}-${fieldName}`;
4197
+ };
4198
+ var FieldClassName = (className, error) => {
4199
+ return error ? `${className} error` : className;
4200
+ };
4201
+ function getFieldState(props) {
4202
+ if (props.error) return "error";
4203
+ if (props.required) return "required";
4204
+ if (props.readOnly) return "readonly";
4205
+ if (props.disabled) return "disabled";
4206
+ return void 0;
4207
+ }
4208
+ function formatDateTime(dateStr, options) {
4209
+ if (!dateStr) return "";
4210
+ const date = new Date(dateStr);
4211
+ if (isNaN(date.getTime())) return dateStr;
4212
+ if (options?.hideTimestamp) {
4213
+ return date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
4214
+ }
4215
+ return date.toLocaleString(void 0, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
4216
+ }
4217
+ function formatDateTimeValue(value) {
4218
+ if (!value) return "";
4219
+ try {
4220
+ return formatDateTime(value);
4221
+ } catch {
4222
+ return String(value);
4223
+ }
4224
+ }
4225
+ function formatDateRange(value) {
4226
+ if (!value) return "";
4227
+ const v = value;
4228
+ if (!v.start && !v.end) return "";
4229
+ if (v.start && v.end) return `${v.start} \u2013 ${v.end}`;
4230
+ return v.start || v.end;
4231
+ }
4232
+ function getFileNames(value) {
4233
+ if (!value) return "";
4234
+ if (Array.isArray(value)) return value.map((f) => f.name).join(", ");
4235
+ return value.name ?? "";
4236
+ }
4237
+ function extractDigits(value) {
4238
+ return value.replace(/\D/g, "");
4239
+ }
4240
+ function formatPhone(digits, format) {
4241
+ if (format === "raw") return digits;
4242
+ if (format === "international") {
4243
+ const d2 = digits.slice(0, 12);
4244
+ if (d2.length === 0) return "";
4245
+ if (d2.length <= 1) return `+${d2}`;
4246
+ if (d2.length <= 4) return `+${d2[0]} ${d2.slice(1)}`;
4247
+ if (d2.length <= 7) return `+${d2[0]} ${d2.slice(1, 4)} ${d2.slice(4)}`;
4248
+ return `+${d2[0]} ${d2.slice(1, 4)} ${d2.slice(4, 7)} ${d2.slice(7)}`;
4249
+ }
4250
+ const d = digits.slice(0, 10);
4251
+ if (d.length === 0) return "";
4252
+ if (d.length <= 3) return `(${d}`;
4253
+ if (d.length <= 6) return `(${d.slice(0, 3)}) ${d.slice(3)}`;
4254
+ return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
4255
+ }
4256
+ function ellipsifyText(value, maxChars) {
4257
+ if (!value || value.length <= maxChars) return value ?? "";
4258
+ const cutoff = maxChars - 3;
4259
+ return `${value.substring(0, cutoff)}...`;
4260
+ }
4261
+ var MAX_FILE_SIZE_MB_DEFAULT = 10;
4262
+ var DocumentLinksStrings = {
4263
+ link: "Link",
4264
+ addLink: "Add Link",
4265
+ addAnotherLink: "Add Another Link",
4266
+ deleteLink: "Delete Link",
4267
+ confirmDeleteLink: "Are you sure you want to delete",
4268
+ delete: "Delete",
4269
+ cancel: "Cancel",
4270
+ saveChanges: "Save Changes",
4271
+ save: "Save"
4272
+ };
4273
+
4124
4274
  // src/helpers/RuleTracer.ts
4125
4275
  var traceEnabled = false;
4126
4276
  var traceLog = [];
@@ -4156,9 +4306,11 @@ export {
4156
4306
  CheckValidDropdownOptions,
4157
4307
  ComponentTypes,
4158
4308
  ConfirmInputsModal_default as ConfirmInputsModal,
4309
+ DocumentLinksStrings,
4159
4310
  ExecuteComputedValue,
4160
4311
  FIELD_PARENT_PREFIX,
4161
4312
  FieldArray,
4313
+ FieldClassName,
4162
4314
  FieldWrapper,
4163
4315
  FormConstants,
4164
4316
  FormDevTools,
@@ -4170,11 +4322,13 @@ export {
4170
4322
  GetComputedValuesOnCreate,
4171
4323
  GetComputedValuesOnDirtyFields,
4172
4324
  GetConfirmInputModalProps,
4325
+ GetFieldDataTestId,
4173
4326
  GetFieldsToRender,
4174
4327
  InitOnCreateFormState,
4175
4328
  InitOnEditFormState,
4176
4329
  InjectedFieldProvider,
4177
4330
  IsExpandVisible,
4331
+ MAX_FILE_SIZE_MB_DEFAULT,
4178
4332
  RenderField_default as RenderField,
4179
4333
  RulesEngineActionType,
4180
4334
  RulesEngineProvider,
@@ -4202,6 +4356,7 @@ export {
4202
4356
  detectDependencyCycles,
4203
4357
  detectSelfDependencies,
4204
4358
  disableRuleTracing,
4359
+ ellipsifyText,
4205
4360
  enableRuleTracing,
4206
4361
  evaluateAffectedFields,
4207
4362
  evaluateAllRules,
@@ -4209,12 +4364,19 @@ export {
4209
4364
  evaluateExpression,
4210
4365
  executeValueFunction,
4211
4366
  extractConditionDependencies,
4367
+ extractDigits,
4212
4368
  extractExpressionDependencies,
4213
4369
  extractFunctionDependencies,
4214
4370
  flushRenderCycle,
4371
+ formatDateRange,
4372
+ formatDateTime,
4373
+ formatDateTimeValue,
4374
+ formatPhone,
4215
4375
  fromRjsfSchema,
4216
4376
  getAllValidatorMetadata,
4217
4377
  getCurrentLocale,
4378
+ getFieldState,
4379
+ getFileNames,
4218
4380
  getLastRenderedFields,
4219
4381
  getLocaleString,
4220
4382
  getRenderCounts,