hs-uix 1.1.0 → 1.2.1

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/form.mjs CHANGED
@@ -136,7 +136,6 @@ var runDefaultFieldValidator = (value, field, allValues) => {
136
136
  if (!isTimeValueObject(value)) return `${errorPrefix} has an invalid time`;
137
137
  break;
138
138
  case "datetime": {
139
- if (isDateValueObject(value)) break;
140
139
  if (!isPlainObject(value)) return `${errorPrefix} has an invalid date/time`;
141
140
  const hasDate = value.date !== void 0;
142
141
  const hasTime = value.time !== void 0;
@@ -203,7 +202,7 @@ var collectAsyncValidatorPromises = (value, field, allValues, context) => {
203
202
  var runValidators = (value, field, allValues, fieldTypes, options = {}) => {
204
203
  const includeCustomValidators = options.includeCustomValidators !== false;
205
204
  const msg = options.messages || {};
206
- if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") return null;
205
+ if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList" || field.type === "fieldGroup") return null;
207
206
  const isRequired = resolveRequired(field, allValues);
208
207
  const plugin = fieldTypes && fieldTypes[field.type];
209
208
  const empty = plugin && plugin.isEmpty ? plugin.isEmpty(value) : isValueEmpty(value, field);
@@ -350,6 +349,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
350
349
  // (values, { reset, rawValues }) => void | Promise
351
350
  transformValues,
352
351
  // (values) => values — reshape before submit
352
+ transformInitialValues,
353
+ // (rawInitialValues) => values — reshape raw data on load
353
354
  onBeforeSubmit,
354
355
  // (values) => boolean | Promise<boolean> — intercept submit
355
356
  onSubmitSuccess,
@@ -513,12 +514,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
513
514
  prevSuccessRef.current = formSuccess;
514
515
  }, [addAlert, formSuccess, successTitle]);
515
516
  const computeInitialValues = () => {
517
+ const resolved = transformInitialValues && initialValues ? transformInitialValues(initialValues) : initialValues;
516
518
  const vals = {};
517
519
  for (const field of fields) {
518
520
  if (field.type === "display" || field.type === "crmPropertyList" || field.type === "crmAssociationPropertyList") continue;
521
+ if (field.type === "fieldGroup" && field.items && field.fields) {
522
+ for (const item of field.items) {
523
+ const subFields = field.fields(item);
524
+ for (const sf of subFields) {
525
+ const plugin2 = fieldTypes && fieldTypes[sf.type];
526
+ const emptyValue2 = plugin2 && plugin2.getEmptyValue ? plugin2.getEmptyValue() : getEmptyValue(sf);
527
+ let init2 = resolved && resolved[sf.name] !== void 0 ? resolved[sf.name] : sf.defaultValue !== void 0 ? sf.defaultValue : emptyValue2;
528
+ if (sf.transformIn) init2 = sf.transformIn(init2);
529
+ vals[sf.name] = init2;
530
+ }
531
+ }
532
+ continue;
533
+ }
519
534
  const plugin = fieldTypes && fieldTypes[field.type];
520
535
  const emptyValue = plugin && plugin.getEmptyValue ? plugin.getEmptyValue() : getEmptyValue(field);
521
- const init = initialValues && initialValues[field.name] !== void 0 ? initialValues[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
536
+ let init = resolved && resolved[field.name] !== void 0 ? resolved[field.name] : field.defaultValue !== void 0 ? field.defaultValue : emptyValue;
537
+ if (field.transformIn) init = field.transformIn(init);
522
538
  vals[field.name] = init;
523
539
  }
524
540
  return vals;
@@ -551,7 +567,14 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
551
567
  formErrorsRef.current = formErrors;
552
568
  const fieldByName = useMemo(() => {
553
569
  const map = /* @__PURE__ */ new Map();
554
- for (const field of fields) map.set(field.name, field);
570
+ for (const field of fields) {
571
+ map.set(field.name, field);
572
+ if (field.type === "fieldGroup" && field.items && field.fields) {
573
+ for (const item of field.items) {
574
+ for (const sf of field.fields(item)) map.set(sf.name, sf);
575
+ }
576
+ }
577
+ }
555
578
  return map;
556
579
  }, [fields]);
557
580
  const isDev = typeof process === "undefined" || !process.env || process.env.NODE_ENV !== "production";
@@ -973,23 +996,27 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
973
996
  );
974
997
  const handleFieldInput = useCallback(
975
998
  (name, value) => {
999
+ handleFieldChange(name, value);
976
1000
  if (!validateOnChange) return;
977
1001
  const err = validateField(name, value);
978
1002
  updateErrors({ [name]: err });
979
1003
  },
980
- [validateOnChange, validateField, updateErrors]
1004
+ [validateOnChange, validateField, updateErrors, handleFieldChange]
981
1005
  );
982
1006
  const handleFieldBlur = useCallback(
983
1007
  (name, value) => {
984
- if (!validateOnBlur) return;
985
1008
  const resolvedValue = value != null ? value : formValuesRef.current[name];
1009
+ if (value != null && value !== formValuesRef.current[name]) {
1010
+ handleFieldChange(name, value);
1011
+ }
1012
+ if (!validateOnBlur) return;
986
1013
  const err = validateField(name, resolvedValue);
987
1014
  updateErrors({ [name]: err });
988
1015
  if (!err) {
989
1016
  triggerAsyncValidation(name, resolvedValue);
990
1017
  }
991
1018
  },
992
- [validateOnBlur, validateField, updateErrors, triggerAsyncValidation]
1019
+ [validateOnBlur, validateField, updateErrors, triggerAsyncValidation, handleFieldChange]
993
1020
  );
994
1021
  const handleSubmit = useCallback(
995
1022
  async (e) => {
@@ -1022,8 +1049,17 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1022
1049
  const rawValues = {};
1023
1050
  for (const key of Object.keys(formValues)) {
1024
1051
  const f = fieldByName.get(key);
1025
- if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList")) continue;
1026
- rawValues[key] = formValues[key];
1052
+ if (f && (f.type === "display" || f.type === "crmPropertyList" || f.type === "crmAssociationPropertyList" || f.type === "fieldGroup")) continue;
1053
+ rawValues[key] = f && f.transformOut ? f.transformOut(formValues[key]) : formValues[key];
1054
+ }
1055
+ for (const f of fields) {
1056
+ if (f.type !== "fieldGroup" || !f.items || !f.fields) continue;
1057
+ for (const item of f.items) {
1058
+ for (const sf of f.fields(item)) {
1059
+ if (formValues[sf.name] === void 0) continue;
1060
+ rawValues[sf.name] = sf.transformOut ? sf.transformOut(formValues[sf.name]) : formValues[sf.name];
1061
+ }
1062
+ }
1027
1063
  }
1028
1064
  const submitValues = transformValues ? transformValues(rawValues) : rawValues;
1029
1065
  if (onBeforeSubmit) {
@@ -1166,10 +1202,125 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1166
1202
  const fieldOnChange = field.debounce ? (v) => handleDebouncedFieldChange(field.name, v) : (v) => handleFieldChange(field.name, v);
1167
1203
  if (field.type === "display") {
1168
1204
  if (field.render) {
1169
- return field.render({ allValues: formValues });
1205
+ return field.render({
1206
+ allValues: formValues,
1207
+ setFieldValue: (name, value) => handleFieldChange(name, value),
1208
+ setFieldError: (name, message) => updateErrors({ [name]: message })
1209
+ });
1170
1210
  }
1171
1211
  return null;
1172
1212
  }
1213
+ if (field.type === "fieldGroup") {
1214
+ const items = field.items || [];
1215
+ const fieldsFn = field.fields;
1216
+ if (!fieldsFn) return null;
1217
+ const groupColumns = field.columns || 1;
1218
+ const showItemLabel = field.showItemLabel !== false;
1219
+ return /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap: "xs" }, field.label && /* @__PURE__ */ React.createElement(Text, { format: { fontWeight: "demibold" } }, field.label), field.description && /* @__PURE__ */ React.createElement(Text, { variant: "microcopy" }, field.description), items.map((item, itemIdx) => {
1220
+ const subFields = fieldsFn(item);
1221
+ return /* @__PURE__ */ React.createElement(Flex, { key: item.key || itemIdx, direction: "row", gap: "xs", align: "end" }, showItemLabel && item.label && /* @__PURE__ */ React.createElement(Box, { flex: 1 }, itemIdx === 0 ? /* @__PURE__ */ React.createElement(
1222
+ Input,
1223
+ {
1224
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
1225
+ label: "\xA0",
1226
+ value: item.label,
1227
+ readOnly: true,
1228
+ disabled: true
1229
+ }
1230
+ ) : /* @__PURE__ */ React.createElement(
1231
+ Input,
1232
+ {
1233
+ name: `_fieldGroup-label-${field.name}-${itemIdx}`,
1234
+ value: item.label,
1235
+ readOnly: true,
1236
+ disabled: true
1237
+ }
1238
+ )), subFields.map((sf) => {
1239
+ const sfValue = formValues[sf.name];
1240
+ const sfError = formErrors[sf.name] || null;
1241
+ const sfLabel = itemIdx === 0 ? sf.label : void 0;
1242
+ const sfReadOnly = sf.readOnly || formReadOnly;
1243
+ const sfDisabled = disabled || sf.disabled || formReadOnly;
1244
+ const sfOnChange = sf.debounce ? (v) => handleDebouncedFieldChange(sf.name, v) : (v) => handleFieldChange(sf.name, v);
1245
+ const sfProps = {
1246
+ name: sf.name,
1247
+ label: sfLabel,
1248
+ placeholder: sf.placeholder,
1249
+ description: itemIdx === 0 ? sf.description : void 0,
1250
+ readOnly: sfReadOnly,
1251
+ disabled: sfDisabled,
1252
+ error: !!sfError,
1253
+ validationMessage: sfError || void 0,
1254
+ ...sf.fieldProps || {}
1255
+ };
1256
+ let sfElement;
1257
+ switch (sf.type) {
1258
+ case "select":
1259
+ sfElement = /* @__PURE__ */ React.createElement(
1260
+ Select,
1261
+ {
1262
+ ...sfProps,
1263
+ value: sfValue,
1264
+ options: resolveOptions(sf, formValues),
1265
+ onChange: sfOnChange
1266
+ }
1267
+ );
1268
+ break;
1269
+ case "number":
1270
+ sfElement = /* @__PURE__ */ React.createElement(
1271
+ NumberInput,
1272
+ {
1273
+ ...sfProps,
1274
+ value: sfValue,
1275
+ onChange: sfOnChange,
1276
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1277
+ }
1278
+ );
1279
+ break;
1280
+ case "toggle":
1281
+ sfElement = /* @__PURE__ */ React.createElement(
1282
+ Toggle,
1283
+ {
1284
+ name: sf.name,
1285
+ label: sfLabel || sf.label,
1286
+ checked: !!sfValue,
1287
+ size: sf.size || "md",
1288
+ labelDisplay: sf.labelDisplay || "top",
1289
+ readonly: sfReadOnly,
1290
+ disabled: sfDisabled,
1291
+ onChange: sfOnChange,
1292
+ ...sf.fieldProps || {}
1293
+ }
1294
+ );
1295
+ break;
1296
+ case "time":
1297
+ sfElement = /* @__PURE__ */ React.createElement(
1298
+ TimeInput,
1299
+ {
1300
+ ...sfProps,
1301
+ value: sfValue,
1302
+ interval: sf.interval,
1303
+ onChange: sfOnChange,
1304
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1305
+ }
1306
+ );
1307
+ break;
1308
+ default:
1309
+ sfElement = /* @__PURE__ */ React.createElement(
1310
+ Input,
1311
+ {
1312
+ ...sfProps,
1313
+ value: sfValue || "",
1314
+ onChange: sfOnChange,
1315
+ onInput: (v) => handleFieldInput(sf.name, v),
1316
+ onBlur: (v) => handleFieldBlur(sf.name, v)
1317
+ }
1318
+ );
1319
+ }
1320
+ return /* @__PURE__ */ React.createElement(Box, { key: sf.name, flex: 1 }, sfElement);
1321
+ }));
1322
+ }));
1323
+ }
1173
1324
  if (field.type === "crmPropertyList") {
1174
1325
  return /* @__PURE__ */ React.createElement(
1175
1326
  CrmPropertyList,
@@ -1714,38 +1865,19 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1714
1865
  }
1715
1866
  return elements;
1716
1867
  };
1717
- const renderLegacyLayout = (fieldSubset) => {
1868
+ const renderSingleColumnLayout = (fieldSubset) => {
1718
1869
  const fieldList = fieldSubset || visibleFields;
1719
- const rows = [];
1720
- let i = 0;
1721
- while (i < fieldList.length) {
1722
- const field = fieldList[i];
1723
- if (field.width === "half" && i + 1 < fieldList.length && fieldList[i + 1].width === "half" && !getDependsOnName(field)) {
1724
- rows.push({ type: "pair", fields: [fieldList[i], fieldList[i + 1]] });
1725
- i += 2;
1726
- } else {
1727
- rows.push({ type: "single", field });
1728
- i++;
1729
- }
1730
- }
1731
1870
  const elements = [];
1732
1871
  const processedDeps = /* @__PURE__ */ new Set();
1733
- for (const row of rows) {
1734
- if (row.type === "pair") {
1735
- elements.push(
1736
- /* @__PURE__ */ React.createElement(Flex, { key: `pair-${row.fields[0].name}`, direction: "row", gap: "sm" }, /* @__PURE__ */ React.createElement(Box, { flex: 1 }, renderField(row.fields[0])), /* @__PURE__ */ React.createElement(Box, { flex: 1 }, renderField(row.fields[1])))
1737
- );
1738
- } else {
1739
- const field = row.field;
1740
- if (processedDeps.has(field.name)) continue;
1741
- elements.push(
1742
- /* @__PURE__ */ React.createElement(React.Fragment, { key: field.name }, renderField(field))
1743
- );
1744
- const dependents = getDependents(field);
1745
- if (dependents.length > 0) {
1746
- for (const dep of dependents) processedDeps.add(dep.name);
1747
- elements.push(renderDependentGroup(field, dependents));
1748
- }
1872
+ for (const field of fieldList) {
1873
+ if (processedDeps.has(field.name)) continue;
1874
+ elements.push(
1875
+ /* @__PURE__ */ React.createElement(React.Fragment, { key: field.name }, renderField(field))
1876
+ );
1877
+ const dependents = getDependents(field);
1878
+ if (dependents.length > 0) {
1879
+ for (const dep of dependents) processedDeps.add(dep.name);
1880
+ elements.push(renderDependentGroup(field, dependents));
1749
1881
  }
1750
1882
  }
1751
1883
  return elements;
@@ -1756,10 +1888,20 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1756
1888
  let batch = [];
1757
1889
  const flushBatch = () => {
1758
1890
  if (batch.length === 0) return;
1759
- const chunks = maxColumns ? Array.from({ length: Math.ceil(batch.length / maxColumns) }, (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)) : [batch];
1760
- for (const chunk of chunks) {
1891
+ if (maxColumns) {
1892
+ const chunks = Array.from(
1893
+ { length: Math.ceil(batch.length / maxColumns) },
1894
+ (_, i) => batch.slice(i * maxColumns, i * maxColumns + maxColumns)
1895
+ );
1896
+ for (const chunk of chunks) {
1897
+ const remainder = maxColumns - chunk.length;
1898
+ elements.push(
1899
+ /* @__PURE__ */ React.createElement(Flex, { key: `ag-${chunk[0].name}`, direction: "row", gap }, chunk.map((f) => /* @__PURE__ */ React.createElement(Box, { key: f.name, flex: 1 }, renderField(f))), remainder > 0 && /* @__PURE__ */ React.createElement(Box, { flex: remainder }))
1900
+ );
1901
+ }
1902
+ } else {
1761
1903
  elements.push(
1762
- /* @__PURE__ */ React.createElement(AutoGrid, { key: `ag-${chunk[0].name}`, columnWidth, flexible: true, gap }, chunk.map((f) => /* @__PURE__ */ React.createElement(React.Fragment, { key: f.name }, renderField(f))))
1904
+ /* @__PURE__ */ React.createElement(AutoGrid, { key: `ag-${batch[0].name}`, columnWidth, flexible: true, gap }, batch.map((f) => /* @__PURE__ */ React.createElement(React.Fragment, { key: f.name }, renderField(f))))
1763
1905
  );
1764
1906
  }
1765
1907
  batch = [];
@@ -1818,7 +1960,7 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1818
1960
  if (layout && fieldSubset === visibleFields) return renderExplicitLayout();
1819
1961
  if (columnWidth) return renderAutoGridLayout(fieldSubset);
1820
1962
  if (columns > 1) return renderGridLayout(fieldSubset);
1821
- return renderLegacyLayout(fieldSubset);
1963
+ return renderSingleColumnLayout(fieldSubset);
1822
1964
  };
1823
1965
  const renderSections = () => {
1824
1966
  const hasSections = Array.isArray(sections) && sections.length > 0;
@@ -1831,7 +1973,8 @@ var FormBuilder = forwardRef(function FormBuilder2(props, ref) {
1831
1973
  for (const sec of sections) {
1832
1974
  const sectionFields = sec.fields ? visibleFields.filter((f) => sec.fields.includes(f.name)) : [];
1833
1975
  if (sectionFields.length === 0) continue;
1834
- const accordionContent = /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, renderFieldSubset(sectionFields));
1976
+ const sectionContext = { values: formValues, errors: formErrors };
1977
+ const accordionContent = /* @__PURE__ */ React.createElement(Flex, { direction: "column", gap }, sec.renderBefore && sec.renderBefore(sectionContext), renderFieldSubset(sectionFields), sec.renderAfter && sec.renderAfter(sectionContext));
1835
1978
  const accordion = /* @__PURE__ */ React.createElement(
1836
1979
  Accordion,
1837
1980
  {