@form-eng/core 1.0.0 → 1.1.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
@@ -76,7 +76,16 @@ var ComponentTypes = {
76
76
  ReadOnlyRichText: "ReadOnlyRichText",
77
77
  ReadOnlyWithButton: "ReadOnlyWithButton",
78
78
  ChoiceSet: "ChoiceSet",
79
- FieldArray: "FieldArray"
79
+ FieldArray: "FieldArray",
80
+ RadioGroup: "RadioGroup",
81
+ CheckboxGroup: "CheckboxGroup",
82
+ Rating: "Rating",
83
+ ColorPicker: "ColorPicker",
84
+ Autocomplete: "Autocomplete",
85
+ FileUpload: "FileUpload",
86
+ DateRange: "DateRange",
87
+ DateTime: "DateTime",
88
+ PhoneInput: "PhoneInput"
80
89
  };
81
90
  var FormConstants = {
82
91
  defaultExpandCutoffCount: 12,
@@ -327,7 +336,7 @@ var FormStrings = {
327
336
  }
328
337
  };
329
338
 
330
- // src/providers/BusinessRulesProvider.tsx
339
+ // src/providers/RulesEngineProvider.tsx
331
340
  import React from "react";
332
341
 
333
342
  // src/helpers/ConditionEvaluator.ts
@@ -386,6 +395,16 @@ function evaluateFieldCondition(condition, values) {
386
395
  } catch {
387
396
  return false;
388
397
  }
398
+ case "arrayContains":
399
+ return Array.isArray(fieldValue) ? fieldValue.some((v) => looseEquals(v, condition.value)) : false;
400
+ case "arrayNotContains":
401
+ return Array.isArray(fieldValue) ? !fieldValue.some((v) => looseEquals(v, condition.value)) : true;
402
+ case "arrayLengthEquals":
403
+ return Array.isArray(fieldValue) ? fieldValue.length === toNumber(condition.value) : false;
404
+ case "arrayLengthGreaterThan":
405
+ return Array.isArray(fieldValue) ? fieldValue.length > toNumber(condition.value) : false;
406
+ case "arrayLengthLessThan":
407
+ return Array.isArray(fieldValue) ? fieldValue.length < toNumber(condition.value) : false;
389
408
  default:
390
409
  return false;
391
410
  }
@@ -695,6 +714,9 @@ function evaluateAllRules(fields, values, areAllFieldsReadonly) {
695
714
  }
696
715
  logEvent("rule_evaluated", fieldName, `${config.rules.length} rule(s) evaluated`);
697
716
  applyEffectToState(fieldStates, fieldName, ruleResults.selfEffect);
717
+ if (ruleResults.pendingSetValue !== void 0 && fieldStates[fieldName]) {
718
+ fieldStates[fieldName].pendingSetValue = ruleResults.pendingSetValue;
719
+ }
698
720
  for (const [targetField, effect] of Object.entries(ruleResults.crossEffects)) {
699
721
  if (fieldStates[targetField]) {
700
722
  applyEffectToState(fieldStates, targetField, effect);
@@ -730,7 +752,8 @@ function evaluateAffectedFields(changedField, fields, values, currentState) {
730
752
  label: config.label,
731
753
  defaultValue: config.defaultValue,
732
754
  computeOnCreateOnly: config.computeOnCreateOnly,
733
- activeRuleIds: []
755
+ activeRuleIds: [],
756
+ pendingSetValue: void 0
734
757
  };
735
758
  }
736
759
  for (const [ownerField, config] of Object.entries(fields)) {
@@ -738,6 +761,9 @@ function evaluateAffectedFields(changedField, fields, values, currentState) {
738
761
  const ruleResults = evaluateFieldRules(config.rules, values);
739
762
  if (affected.has(ownerField) || ownerField === changedField) {
740
763
  applyEffectToState(updatedStates, ownerField, ruleResults.selfEffect);
764
+ if (ruleResults.pendingSetValue !== void 0 && updatedStates[ownerField]) {
765
+ updatedStates[ownerField].pendingSetValue = ruleResults.pendingSetValue;
766
+ }
741
767
  }
742
768
  for (const [targetField, effect] of Object.entries(ruleResults.crossEffects)) {
743
769
  if (affected.has(targetField) && updatedStates[targetField]) {
@@ -771,6 +797,7 @@ function evaluateFieldRules(rules, values) {
771
797
  const crossEffects = {};
772
798
  let fieldOrder;
773
799
  const activeRuleIds = [];
800
+ let pendingSetValue;
774
801
  for (let i = 0; i < sorted.length; i++) {
775
802
  const rule = sorted[i];
776
803
  const conditionMet = evaluateCondition(rule.when, values);
@@ -778,6 +805,9 @@ function evaluateFieldRules(rules, values) {
778
805
  activeRuleIds.push(rule.id ?? `rule_${i}`);
779
806
  }
780
807
  const effect = conditionMet ? rule.then : rule.else;
808
+ if (conditionMet && rule.then?.setValue !== void 0 && pendingSetValue === void 0) {
809
+ pendingSetValue = { value: rule.then.setValue };
810
+ }
781
811
  if (!effect) continue;
782
812
  mergeEffect(selfEffect, effect);
783
813
  if (effect.fields) {
@@ -792,7 +822,7 @@ function evaluateFieldRules(rules, values) {
792
822
  fieldOrder = effect.fieldOrder;
793
823
  }
794
824
  }
795
- return { selfEffect, crossEffects, fieldOrder, activeRuleIds };
825
+ return { selfEffect, crossEffects, fieldOrder, activeRuleIds, pendingSetValue };
796
826
  }
797
827
  function mergeEffect(target, source) {
798
828
  if (source.required !== void 0 && target.required === void 0) {
@@ -836,7 +866,7 @@ function applyEffectToState(states, fieldName, effect) {
836
866
  if (effect.computedValue !== void 0) state.computedValue = effect.computedValue;
837
867
  }
838
868
 
839
- // src/reducers/BusinessRulesReducer.ts
869
+ // src/reducers/RulesEngineReducer.ts
840
870
  var defaultRulesEngineState = {
841
871
  configs: {}
842
872
  };
@@ -883,9 +913,9 @@ var rulesEngineReducer = (state = defaultRulesEngineState, action) => {
883
913
  return state;
884
914
  }
885
915
  };
886
- var BusinessRulesReducer_default = rulesEngineReducer;
916
+ var RulesEngineReducer_default = rulesEngineReducer;
887
917
 
888
- // src/providers/BusinessRulesProvider.tsx
918
+ // src/providers/RulesEngineProvider.tsx
889
919
  import { jsx } from "react/jsx-runtime";
890
920
  var RulesEngineContext = React.createContext(
891
921
  void 0
@@ -900,7 +930,7 @@ function UseRulesEngineContext() {
900
930
  return context;
901
931
  }
902
932
  var RulesEngineProvider = (props) => {
903
- const [rulesState, dispatch] = React.useReducer(BusinessRulesReducer_default, defaultRulesEngineState);
933
+ const [rulesState, dispatch] = React.useReducer(RulesEngineReducer_default, defaultRulesEngineState);
904
934
  const rulesStateRef = React.useRef(rulesState);
905
935
  React.useEffect(() => {
906
936
  rulesStateRef.current = rulesState;
@@ -939,7 +969,7 @@ var RulesEngineProvider = (props) => {
939
969
  return /* @__PURE__ */ jsx(RulesEngineContext.Provider, { value: providerValue, children: props.children });
940
970
  };
941
971
 
942
- // src/providers/InjectedHookFieldProvider.tsx
972
+ // src/providers/InjectedFieldProvider.tsx
943
973
  import React2 from "react";
944
974
  import { jsx as jsx2 } from "react/jsx-runtime";
945
975
  var InjectedFieldContext = React2.createContext(
@@ -1083,6 +1113,19 @@ var requiredIf = (value, params, context) => {
1083
1113
  }
1084
1114
  return void 0;
1085
1115
  };
1116
+ var validatorMetadataRegistry = {};
1117
+ function registerValidatorMetadata(name, metadata) {
1118
+ validatorMetadataRegistry = { ...validatorMetadataRegistry, [name]: metadata };
1119
+ }
1120
+ function getValidatorMetadata(name) {
1121
+ return validatorMetadataRegistry[name];
1122
+ }
1123
+ function getAllValidatorMetadata() {
1124
+ return { ...validatorMetadataRegistry };
1125
+ }
1126
+ function resetValidatorMetadataRegistry() {
1127
+ validatorMetadataRegistry = {};
1128
+ }
1086
1129
  var defaultValidators = {
1087
1130
  required,
1088
1131
  email,
@@ -1382,7 +1425,7 @@ function validateDependencyGraph(fields) {
1382
1425
  try {
1383
1426
  if (typeof globalThis !== "undefined" && globalThis.__DEV__ !== false) {
1384
1427
  for (const error of errors) {
1385
- console.warn(`[dynamic-forms] ${error.message}`);
1428
+ console.warn(`[form-engine] ${error.message}`);
1386
1429
  }
1387
1430
  }
1388
1431
  } catch {
@@ -1688,7 +1731,7 @@ var FieldWrapper = React5.memo((props) => {
1688
1731
  /* @__PURE__ */ jsxs3("label", { id: labelId, htmlFor: id, className: "field-label", children: [
1689
1732
  label,
1690
1733
  required2 && /* @__PURE__ */ jsxs3(Fragment, { children: [
1691
- /* @__PURE__ */ jsx5("span", { className: "required-indicator", "aria-hidden": "true", style: { color: "var(--form-required-color, #d13438)" }, children: " *" }),
1734
+ /* @__PURE__ */ jsx5("span", { className: "required-indicator", "aria-hidden": "true", style: { color: "var(--fe-required-color, #d13438)" }, children: " *" }),
1692
1735
  /* @__PURE__ */ jsx5("span", { className: "sr-only", style: { position: "absolute", width: "1px", height: "1px", padding: 0, margin: "-1px", overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }, children: " (required)" })
1693
1736
  ] })
1694
1737
  ] }),
@@ -1696,19 +1739,19 @@ var FieldWrapper = React5.memo((props) => {
1696
1739
  !additionalInfoComponent && additionalInfo && /* @__PURE__ */ jsx5("span", { className: "additional-info", title: additionalInfo, children: "\u24D8" })
1697
1740
  ] });
1698
1741
  const defaultErrorAndStatus = /* @__PURE__ */ jsx5("div", { className: "message", children: error ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1699
- /* @__PURE__ */ jsx5("span", { className: "error-icon", "aria-hidden": "true", style: { color: "var(--form-error-color, #d13438)" }, children: "\u2716" }),
1700
- /* @__PURE__ */ jsx5("span", { className: "error-message", id: errorMessageId, role: "alert", style: { color: "var(--form-error-color, #d13438)" }, children: error.message || "Error" })
1742
+ /* @__PURE__ */ jsx5("span", { className: "error-icon", "aria-hidden": "true", style: { color: "var(--fe-error-color, #d13438)" }, children: "\u2716" }),
1743
+ /* @__PURE__ */ jsx5("span", { className: "error-message", id: errorMessageId, role: "alert", style: { color: "var(--fe-error-color, #d13438)" }, children: error.message || "Error" })
1701
1744
  ] }) : savePending ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1702
- /* @__PURE__ */ jsx5("span", { className: "warning-icon", "aria-hidden": "true", style: { color: "var(--form-warning-color, #ffb900)" }, children: "\u26A0" }),
1703
- /* @__PURE__ */ jsxs3("span", { className: "warning-message", id: errorMessageId, role: "status", style: { color: "var(--form-warning-color, #ffb900)" }, children: [
1745
+ /* @__PURE__ */ jsx5("span", { className: "warning-icon", "aria-hidden": "true", style: { color: "var(--fe-warning-color, #ffb900)" }, children: "\u26A0" }),
1746
+ /* @__PURE__ */ jsxs3("span", { className: "warning-message", id: errorMessageId, role: "status", style: { color: "var(--fe-warning-color, #ffb900)" }, children: [
1704
1747
  !isManualSave ? FormStrings.autoSavePending : FormStrings.savePending,
1705
1748
  " (",
1706
1749
  `${errorCount} ${FormStrings.remaining}`,
1707
1750
  ")"
1708
1751
  ] })
1709
1752
  ] }) : saving ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1710
- /* @__PURE__ */ jsx5("span", { className: "save-spinner", "aria-hidden": "true", style: { color: "var(--form-saving-color, #0078d4)" }, children: "\u231B" }),
1711
- /* @__PURE__ */ jsx5("span", { className: "save-message", id: errorMessageId, role: "status", style: { color: "var(--form-saving-color, #0078d4)" }, children: FormStrings.saving })
1753
+ /* @__PURE__ */ jsx5("span", { className: "save-spinner", "aria-hidden": "true", style: { color: "var(--fe-saving-color, #0078d4)" }, children: "\u231B" }),
1754
+ /* @__PURE__ */ jsx5("span", { className: "save-message", id: errorMessageId, role: "status", style: { color: "var(--fe-saving-color, #0078d4)" }, children: FormStrings.saving })
1712
1755
  ] }) : /* @__PURE__ */ jsx5(Fragment, {}) });
1713
1756
  return /* @__PURE__ */ jsxs3(
1714
1757
  "div",
@@ -2027,7 +2070,7 @@ var ConfirmInputsModal = (props) => {
2027
2070
  trigger();
2028
2071
  setValue(`${fieldName}`, fieldValue, { shouldDirty: true });
2029
2072
  };
2030
- const content = /* @__PURE__ */ jsx7("div", { className: "dynamic-form-wrapper", children: /* @__PURE__ */ jsx7("div", { className: "dynamic-form-container", children: /* @__PURE__ */ jsx7("form", { className: "dynamic-form modal", children: confirmInputFields?.map((confirmInputField) => {
2073
+ const content = /* @__PURE__ */ jsx7("div", { className: "fe-form-wrapper", children: /* @__PURE__ */ jsx7("div", { className: "fe-form-container", children: /* @__PURE__ */ jsx7("form", { className: "fe-form fe-modal", children: confirmInputFields?.map((confirmInputField) => {
2031
2074
  const fieldState = rulesState.configs[configName]?.fieldStates[confirmInputField];
2032
2075
  const fieldConfig = fields[confirmInputField];
2033
2076
  if (!fieldState || !fieldConfig) return null;
@@ -2055,9 +2098,9 @@ var ConfirmInputsModal = (props) => {
2055
2098
  if (renderDialog) {
2056
2099
  return renderDialog({ isOpen: !!isOpen, onSave: saveConfirmInputFields, onCancel: cancelConfirmInputFields, children: content });
2057
2100
  }
2058
- return /* @__PURE__ */ jsxs5("dialog", { ref: dialogRef, className: "dynamic-form-modal", role: "dialog", "aria-modal": "true", "aria-label": FormStrings.confirm, onKeyDown: handleKeyDown, children: [
2101
+ return /* @__PURE__ */ jsxs5("dialog", { ref: dialogRef, className: "fe-modal", role: "dialog", "aria-modal": "true", "aria-label": FormStrings.confirm, onKeyDown: handleKeyDown, children: [
2059
2102
  content,
2060
- /* @__PURE__ */ jsxs5("div", { className: "dynamic-form-modal-actions", children: [
2103
+ /* @__PURE__ */ jsxs5("div", { className: "fe-modal-actions", children: [
2061
2104
  /* @__PURE__ */ jsx7("button", { type: "button", ref: saveButtonRef, onClick: saveConfirmInputFields, children: FormStrings.save }),
2062
2105
  /* @__PURE__ */ jsx7("button", { type: "button", onClick: cancelConfirmInputFields, children: FormStrings.cancel })
2063
2106
  ] })
@@ -2129,10 +2172,10 @@ var FormFields = (props) => {
2129
2172
  const collapsedClass = !isExpanded && (expandEnabled || expandEnabled === void 0) ? "collapsed" : "";
2130
2173
  const fieldsToRender = GetFieldsToRender(fieldRenderLimit ?? 0, fieldOrder ?? [], formState?.fieldStates);
2131
2174
  const loadingKey = `${programName}-${entityType}-${entityId}-form-loaded`;
2132
- return /* @__PURE__ */ jsx9("div", { className: `dynamic-form-container ${collapsedClass}`, children: /* @__PURE__ */ jsxs7(
2175
+ return /* @__PURE__ */ jsx9("div", { className: `fe-form-container ${collapsedClass}`, children: /* @__PURE__ */ jsxs7(
2133
2176
  "form",
2134
2177
  {
2135
- className: `dynamic-form ${collapsedClass} ${inPanel ? "in-panel" : ""}`,
2178
+ className: `fe-form ${collapsedClass} ${inPanel ? "in-panel" : ""}`,
2136
2179
  style: collapsedClass && collapsedMaxHeight ? { maxHeight: `${collapsedMaxHeight}px` } : void 0,
2137
2180
  "data-testid": `${programName}-${entityType}-${entityId}-form`,
2138
2181
  children: [
@@ -2414,9 +2457,9 @@ var FormEngine = (props) => {
2414
2457
  const cutoff = expandEnabled && !isExpanded ? effectiveExpandCutoff ?? FormConstants.defaultExpandCutoffCount : void 0;
2415
2458
  return /* @__PURE__ */ jsxs8(FormProvider, { ...formMethods, formState: { ...formMethods.formState, isDirty, isValid, dirtyFields, errors, isSubmitting, isSubmitSuccessful }, children: [
2416
2459
  /* @__PURE__ */ jsx10("div", { role: "status", "aria-live": "polite", className: "sr-only", style: { position: "absolute", width: "1px", height: "1px", padding: 0, margin: "-1px", overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }, "data-testid": "form-status-live-region", children: statusMessage }),
2417
- enableFilter && /* @__PURE__ */ jsx10("div", { className: "dynamic-form-filter", children: renderFilterInput ? renderFilterInput({ onChange: onFilterChange }) : /* @__PURE__ */ jsx10("input", { type: "text", placeholder: FormStrings.filterFields, "aria-label": FormStrings.filterFields, onChange: (e) => onFilterChange(e.target.value), className: "dynamic-form-filter-input" }) }),
2418
- formErrors && formErrors.length > 0 && /* @__PURE__ */ jsx10("div", { className: "form-errors", role: "alert", style: { color: "var(--form-error-color, #d13438)", padding: "8px", marginBottom: "8px" }, children: formErrors.map((err, i) => /* @__PURE__ */ jsx10("div", { className: "form-error-item", children: err }, i)) }),
2419
- /* @__PURE__ */ jsxs8("div", { className: "dynamic-form-wrapper", children: [
2460
+ enableFilter && /* @__PURE__ */ jsx10("div", { className: "fe-filter", children: renderFilterInput ? renderFilterInput({ onChange: onFilterChange }) : /* @__PURE__ */ jsx10("input", { type: "text", placeholder: FormStrings.filterFields, "aria-label": FormStrings.filterFields, onChange: (e) => onFilterChange(e.target.value), className: "fe-filter-input" }) }),
2461
+ formErrors && formErrors.length > 0 && /* @__PURE__ */ jsx10("div", { className: "form-errors", role: "alert", style: { color: "var(--fe-error-color, #d13438)", padding: "8px", marginBottom: "8px" }, children: formErrors.map((err, i) => /* @__PURE__ */ jsx10("div", { className: "form-error-item", children: err }, i)) }),
2462
+ /* @__PURE__ */ jsxs8("div", { className: "fe-form-wrapper", children: [
2420
2463
  /* @__PURE__ */ jsx10(
2421
2464
  FormFields,
2422
2465
  {
@@ -2443,7 +2486,7 @@ var FormEngine = (props) => {
2443
2486
  }
2444
2487
  ),
2445
2488
  expandEnabled && (renderExpandButton ? renderExpandButton({ isExpanded, onToggle: () => setIsExpanded(!isExpanded) }) : /* @__PURE__ */ jsx10("button", { className: "expand-button", onClick: () => setIsExpanded(!isExpanded), "aria-expanded": isExpanded, "data-testid": `${programName}-${entityType}-${entityId}-expand-form`, children: isExpanded ? FormStrings.seeLess : FormStrings.expand })),
2446
- effectiveManualSave && (renderSaveButton ? renderSaveButton({ onSave: manualSave, isDirty, isValid, isSubmitting }) : /* @__PURE__ */ jsxs8("div", { className: "dynamic-form-save-actions", style: { marginTop: "16px", display: "flex", gap: "8px" }, children: [
2489
+ effectiveManualSave && (renderSaveButton ? renderSaveButton({ onSave: manualSave, isDirty, isValid, isSubmitting }) : /* @__PURE__ */ jsxs8("div", { className: "fe-save-actions", style: { marginTop: "16px", display: "flex", gap: "8px" }, children: [
2447
2490
  /* @__PURE__ */ jsx10("button", { type: "button", className: "save-button", onClick: manualSave, disabled: !isDirty || isSubmitting, children: isCreate ? FormStrings.create : FormStrings.save }),
2448
2491
  /* @__PURE__ */ jsx10("button", { type: "button", className: "cancel-button", onClick: () => {
2449
2492
  reset();
@@ -4170,6 +4213,7 @@ export {
4170
4213
  extractFunctionDependencies,
4171
4214
  flushRenderCycle,
4172
4215
  fromRjsfSchema,
4216
+ getAllValidatorMetadata,
4173
4217
  getCurrentLocale,
4174
4218
  getLastRenderedFields,
4175
4219
  getLocaleString,
@@ -4181,6 +4225,7 @@ export {
4181
4225
  getTimeline,
4182
4226
  getTotalFormRenders,
4183
4227
  getValidator,
4228
+ getValidatorMetadata,
4184
4229
  getValidatorRegistry,
4185
4230
  getValueFunction,
4186
4231
  getVisibleSteps,
@@ -4193,10 +4238,12 @@ export {
4193
4238
  isStringEmpty,
4194
4239
  logEvent,
4195
4240
  registerLocale,
4241
+ registerValidatorMetadata,
4196
4242
  registerValidators,
4197
4243
  registerValueFunctions,
4198
4244
  resetLocale,
4199
4245
  resetRenderTracker,
4246
+ resetValidatorMetadataRegistry,
4200
4247
  resetValidatorRegistry,
4201
4248
  resetValueFunctionRegistry,
4202
4249
  runSyncValidations,